/*****************************************************************************
 *                   Projet MESI : Algo génétique Asynchrone                 *
 *                      Guillaume MONTAVON & Renaud DAVID                    *
 *                                                                           *
 *               Fichier contenant le code de tous les cients                *
 *                  ainsi que toutes les variables globales                  *
 *****************************************************************************/

var nbCreation = 0;
var nbMutation = 0;
var nbEvaluation = 0;
var nbReproduction = 0;
var nbPredation = 0;
var formule; //formule d'évaluation
var structureIndiv; //structure de tous les individus : nombre de gènes, ... (format JSON)
var tempsMinAttente = 10; //temps d'attente par défaut entre chaque requête
var tempsCreation = tempsMinAttente;
var tempsReproduction = tempsMinAttente;
var tempsEvaluation = tempsMinAttente/2;
var tempsPredation = tempsMinAttente*2;
var traitementEnCours = true; //le traitement du problème n'est pas terminé
var freqMutation1pour = 100; //pour 100 individus créés, on fera 1 mutation

//lance tous les clients en parallèle
function clientGestionIndiv() {
	clientEvaluation();
	clientReproduction();
	clientMutation();
	clientInitialisation();
}

//client d'initialisation
function clientInitialisation() {
	
	//demande au serveur la structure des individus pour pouvoir ensuite les créer
	requeteServeur("demanderStructureIndividu", 'structureIndiv = JSONobject;getEtatPop(\'initialisation\');');

}

//client d'initialisation : on créer des individus et on les envoie si le traitement n'est pas terminé
function initialisation(JSONobject) {

	if(!traitementTermine(JSONobject.trait)) {

		if(JSONobject.actif < JSONobject.minActif) {
			envoyerNvIndiv();
			tempsCreation = tempsMinAttente;
		}
		else {
			//sinon on attend le double de temps
			tempsCreation *= 2;
			setTimeout("getEtatPop(\'initialisation\');", tempsCreation);
		}
	
	}

}

//client d'initialisation : on envoie au serveur un individu que l'on crée auparavant
function envoyerNvIndiv() {

	try {		
		
		var JSONObject = new Object();
		JSONObject.liste = new Array();
		
		for(var i = 0; i < structureIndiv.liste_gene.length; i++) {
			JSONObject.liste[i]=new Object();
			if(structureIndiv.nom_codage == "binaire") {
				JSONObject.liste[i].valeurB = creerAllele(structureIndiv.liste_gene[i].taille, structureIndiv.nom_codage, structureIndiv.liste_gene[i].valeur_min, 
										structureIndiv.liste_gene[i].valeur_max, structureIndiv.liste_gene[i].finesse);
				JSONObject.liste[i].valeurR = parseInt(JSONObject.liste[i].valeurB, 2);
			}
			else {
				JSONObject.liste[i].valeurR = creerAllele(structureIndiv.liste_gene[i].taille, structureIndiv.nom_codage, structureIndiv.liste_gene[i].valeur_min, 
										structureIndiv.liste_gene[i].valeur_max, structureIndiv.liste_gene[i].finesse);
				JSONObject.liste[i].valeurB = ajouteZero(JSONObject.liste[i].valeurR.toString(2), structureIndiv.liste_gene[i].taille);
			}
			JSONObject.liste[i].id = structureIndiv.liste_gene[i].id_gene;
		}
		
		JSONstring = JSON.stringify(JSONObject);
		
		var request;
		var formData = new FormData();  
		
		formData.append("liste_gene", JSONstring);
		request = getXhr();
		request.onreadystatechange = function() {
			// On ne fait quelque chose que si on a tout reçu et que le serveur est ok
			if(request.readyState == 4 && request.status == 200) {
				MAJNbOperationTermine("creationIndiv");
				setTimeout("getEtatPop(\'initialisation\');", tempsCreation);
			}
		};
		
		request.open("POST", "requete.php?objet=creerIndividu", true);
		request.send(formData);
		
	} catch(e) {
		alert("erreur : " + e);
	}

}

//client d'initialisation : on retourne une allèle choisie aléatoirement en fonction des paramètres
function creerAllele(taille, codage, valeurMin, valeurMax, finesse) {

	if(codage == "binaire") {
		var retour = "";
		for(var i = 0; i < taille; i++)
			retour += Math.round(Math.random());
	}
	else
		var retour = Math.round((Math.random() * (valeurMax-valeurMin) + valeurMin)*finesse)/finesse;

	return retour;

}

//client d'évaluation
function clientEvaluation() {
	
	//demande la formule puis on lance la fonction evaluation
	requeteServeur("obtenirFonctionEval", 'formule = JSONobject.formule;getEtatPop(\'evaluer\');');

}

//client d'évaluationon : on vérifie si on a besoin d'évaluer des individus
function evaluer(JSONobject) {

	if(!traitementTermine(JSONobject.trait)) {

		if(JSONobject.nonEval > 0) {
			requeteServeur("obtenirIndivNonEval", "evaluerIndiv(JSONobject);");
			tempsEvaluation = tempsMinAttente/2;
		}
		else {
			//sinon on attend le double de temps
			tempsEvaluation *= 2;
			setTimeout("getEtatPop(\'evaluer\');", tempsEvaluation);
		}
	}
	
}

//client d'évaluationon : evaluation de l'individu puis on l'envoie au serveur
function evaluerIndiv(JSONobject){
	
	eval(formule);
	var chaine = "res = evaluation(";

	for(var i = 0; i < JSONobject.liste_gene.length; i++) {
		if (i != 0)
			chaine += ",";
		chaine += JSONobject.liste_gene[i].alleleR;
	}
	chaine += ")";
	eval(chaine);
	
	requeteServeur("evaluer&idIndiv=" + JSONobject.id_indiv + "&fitness=" + res, 'MAJNbOperationTermine("evaluationIndiv");setTimeout("getEtatPop(\'evaluer\');", ' + tempsPredation + ');');

}

//client de reproduction
function clientReproduction() {

	getEtatPop('reproduction');

}

//client de reproduction : demande au serveur de lui donnée 2 individus pour réaliser un croisement
function reproduction(JSONobject) {
	
	if(!traitementTermine(JSONobject.trait)) {

		if(JSONobject.actif < JSONobject.maxActif && JSONobject.actif >= JSONobject.minActif && JSONobject.total < JSONobject.maxCrea && JSONobject.eval >= 2) {
			requeteServeur("selection", 'fonctionReproduction(JSONobject);');
			tempsReproduction = tempsMinAttente;
		}
		else {
			tempsReproduction *= 2;
			setTimeout("clientReproduction();", tempsReproduction);
		}
	
	}
	
}

//client de reproduction : génère 2 enfants avec les parents données en paramètre, puis les envoi au serveur
function fonctionReproduction(JSONobject) {

	var formData = new FormData();
	formData.append("listeEnfants", creerEnfants(JSONobject));
	var request = getXhr();
	request.onreadystatechange = function() {
		// On ne fait quelque chose que si on a tout reçu et que le serveur est ok
		if(request.readyState == 4 && request.status == 200) {
			MAJNbOperationTermine("reproductionIndiv");
			setTimeout("clientReproduction();", tempsReproduction);
		}
	};
	request.open("POST", "requete.php?objet=reproduction", true);
	request.send(formData);
	
}

//client de reproduction : génère 2 enfants en fonction des caractéristiques des 2 parents
function creerEnfants(JSONobject) {
	
	var JSONObject = new Object();
	JSONObject.listeIndiv = new Array();
	JSONObject.listeParents = new Array();
	
	try {
	
		//var nbParents = JSONobject.liste_indiv.length;
		var nbParents = 2; // fonctionne seulement avec 2 parents
		var nbGene = JSONobject.liste_indiv[0].liste_gene.length;
		
		//on choisi un gène qui contiendra la coupure
		var geneChoisi = Math.round(Math.random() * (nbGene - 1));
		
		//on choisi la position de la coupure
		var positionChoisie = Math.round(Math.random() * (JSONobject.liste_indiv[0].liste_gene[geneChoisi].alleleB.length - 1));

		var enfant = new Array();
		
		//on parcourt la liste de parents
		for(var enfantCourant = 0; enfantCourant < nbParents; enfantCourant++) {
			JSONObject.listeIndiv[enfantCourant]=new Object();
			JSONObject.listeIndiv[enfantCourant].listeGene = new Array();
			enfant[enfantCourant] = new Array();
			
			//on ajoute les id des parents pour connaitre les liens de parentés
			JSONObject.listeParents[enfantCourant] = new Object();			
			JSONObject.listeParents[enfantCourant].idParent = JSONobject.liste_indiv[enfantCourant].id_indiv;
			
			//on parcourt la liste de gènes de tous les parents
			for(var geneCourant = 0; geneCourant < nbGene; geneCourant++) {
				
				if(geneCourant == geneChoisi)
					enfant[enfantCourant][geneCourant] = JSONobject.liste_indiv[enfantCourant].liste_gene[geneCourant].alleleB.substring(0,positionChoisie) + JSONobject.liste_indiv[inverse(enfantCourant)].liste_gene[geneCourant].alleleB.substring(positionChoisie);
				else if(geneCourant < geneChoisi)
					enfant[enfantCourant][geneCourant] = JSONobject.liste_indiv[enfantCourant].liste_gene[geneCourant].alleleB;
				else
					enfant[enfantCourant][geneCourant] = JSONobject.liste_indiv[inverse(enfantCourant)].liste_gene[geneCourant].alleleB;
			
				//on ajoute les gènes générés pour chaque enfants dans le JSON
				JSONObject.listeIndiv[enfantCourant].listeGene[geneCourant] = new Object();
				JSONObject.listeIndiv[enfantCourant].listeGene[geneCourant].idGene = JSONobject.liste_indiv[enfantCourant].liste_gene[geneCourant].id_gene;
				JSONObject.listeIndiv[enfantCourant].listeGene[geneCourant].alleleB = enfant[enfantCourant][geneCourant];
				JSONObject.listeIndiv[enfantCourant].listeGene[geneCourant].alleleR = parseInt(enfant[enfantCourant][geneCourant], 2);
			}
		}
		
	} catch(e) {
		alert("Erreur : " + e);
	}
	
	return JSON.stringify(JSONObject);
	
}

//client de reproduction : Choisi un gène puis modifie un bit de son allèle (mutation) et retourne sous forme de JSON l'individu
function modifieGene(JSONobject) {
	
	var JSONObject = new Object();
	JSONObject.idIndiv = JSONobject.id_indiv;
	JSONObject.listeGene = new Array();
	
	//on choisi un gène
	var geneChoisi = Math.round(Math.random() * (JSONobject.liste_gene.length - 1));
	
	for(var i = 0; i < JSONobject.liste_gene.length; i++) {
		JSONObject.listeGene[i]=new Object();
		JSONObject.listeGene[i].idGene = JSONobject.liste_gene[i].id_gene;
		if(i == geneChoisi) {
			JSONObject.listeGene[i].alleleB = modifieAllele(JSONobject.liste_gene[i].alleleB);
			JSONObject.listeGene[i].alleleR = parseInt(JSONObject.listeGene[i].alleleB, 2);
		}
		else {
			//les gènes non choisis ne sont pas modifié
			JSONObject.listeGene[i].alleleB = JSONobject.liste_gene[i].alleleB;
			JSONObject.listeGene[i].alleleR = JSONobject.liste_gene[i].alleleR;
		}
	}
	
	return JSON.stringify(JSONObject);
	
}

//client de reproduction : Inverse un bit d'une allele donné en paramètre (allele), utile pour la mutation
function modifieAllele(allele) {
	
	var positionChoisie = Math.round(Math.random() * allele.length - 1);
	var bitModif = allele.charAt(positionChoisie);
	if(bitModif == "0")
		bitModif = "1";
	else
		bitModif = "0";
	allele = allele.substring(0,positionChoisie) + bitModif + allele.substring(positionChoisie + 1);
	//allele.substring(1);
	
	return allele;
}

//client de mutation
function clientMutation() {

	if(verifierMutationAFaire() && traitementEnCours)
		requeteServeur("obtenirIndiv", 'mutation(JSONobject);');

	setTimeout("clientMutation();", 500);
	
}

//client de mutation : réalise une mutation d'un gène d'un individu choisi aléatoirement par le serveur
function mutation(JSONobject) {

	var formData = new FormData();  
	formData.append("jsonIndiv", modifieGene(JSONobject));
	var request = getXhr();
	request.onreadystatechange = function() {
		// On ne fait quelque chose que si on a tout reçu et que le serveur est ok
		if(request.readyState == 4 && request.status == 200) {
			MAJNbOperationTermine("mutationIndiv");
		}
	};
	request.open("POST", "requete.php?objet=updateIndiv&id_probleme=" + idProbleme, true);
	request.send(formData);

}

//client de prédation
function clientPredation() {
	getEtatPop('predation');
}

//client de prédation : marque des individus comme non-vivant si la taille de la population dépasse la limite fixé.
//En fonction du nombre d’individu créés depuis le début de l’optimisation le client de prédation
//peut signifier au serveur d'arrêter le traitement.
function predation(JSONobject) {

	if(!traitementTermine(JSONobject.trait)) {

		if(JSONobject.total >= JSONobject.maxCrea && JSONobject.nonEval <= 0) {
			//on arrête le traitement du problème
			traitementTermine(0);
			requeteServeur("arreterTraitement", "");
		}
		else if(JSONobject.actif > JSONobject.minActif) {
			//tue un individu
			requeteServeur("tuerIndiv", 'incNbPredation();setTimeout("clientPredation();", ' + tempsPredation + ');');
			tempsPredation = tempsMinAttente * 2;
		}
		else {
			//sinon on attend le double de temps
			tempsPredation *= 2;
			setTimeout("clientPredation();", tempsPredation);
		}
	
	}
	
}

//Incrémente le nombre d'individus suivant le paramètre (nbMutation, nbCreation, ...)
function MAJNbOperationTermine(divAModifier) {

	document.getElementById("creationIndiv").style.color = "black";
	document.getElementById("mutationIndiv").style.color = "black";
	document.getElementById("evaluationIndiv").style.color = "black";
	document.getElementById("reproductionIndiv").style.color = "black";
	
	var nbCourant = 0;
	if(divAModifier == "creationIndiv") {
		nbCreation++;
		nbCourant = nbCreation;
	}
	else if(divAModifier == "mutationIndiv") {
		nbMutation++;
		nbCourant = nbMutation;
	}
	else if(divAModifier == "evaluationIndiv") {
		nbEvaluation++;
		nbCourant = nbEvaluation;
	}
	else if(divAModifier == "reproductionIndiv") {
		nbReproduction += 2;
		nbCourant = nbReproduction;
	}
	
	document.getElementById(divAModifier).innerHTML = nbCourant + "";
	document.getElementById(divAModifier).style.color = "red";
}

//incrémente le nombre d'individus tué depuis le début
function incNbPredation() {
	nbPredation++;
	document.getElementById("predationIndiv").innerHTML = nbPredation + "";
	document.getElementById("predationIndiv").style.color = "red";
}

//teste si le traitement du problème est terminé ou non
function traitementTermine(trait) {

	if(trait == 0) {
		document.getElementById("loader").style.display = "none";
		if(traitementEnCours)
			alert("Traitement terminé pour ce problème");
		traitementEnCours = false;
		return true;
	}
	else
		return false;

}

//on demande l'état de la population (nombre de création, ...) puis on exécute la fonction donnée en paramètre
function getEtatPop(nomFonction) {
	requeteServeur("etatPop", nomFonction + '(JSONobject);');
}

//permet de vérifier si on doit faire une muation (dépend du taux de mutation : freqMutation1pour)
function verifierMutationAFaire() {
	return (nbCreation + nbReproduction > (nbMutation + 1) * freqMutation1pour);
}